package com.atguigu.guli.gmall.gateway.filter;

import com.atguigu.gmall.common.exception.GmallException;
import com.atguigu.gmall.common.utils.IpUtils;
import com.atguigu.gmall.common.utils.JwtUtils;
import com.atguigu.guli.gmall.gateway.config.JwtProperties;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/*
    登录状态验证的filter：
        1、访问目标服务时，如果请求的路由无需登录状态验证，放行请求
        2、访问目标服务的请求需要验证登录状态：
            2.1 已登录
                2.1.1 已登录 客户端的ip和token中的ip相同 放行请求
                2.1.2 已登录 客户端ip和token的ip不同 拦截请求
            2.2 未登录   拦截请求

            上面获取token解析时 如果有异常(token过期了或者被篡改了)  拦截请求

        拦截请求：重定向跳转到登录页面重新登录即可，
                登录成功后还需要跳转会登录之前的页面(给重定向配置returnUrl的参数值，值为登录之前的url地址)


        暂时使用auth服务的路由测试：
                给auth服务路由设置无需登录验证的允许访问的路径地址(toLogin.html,login)

 */
@Component
@Slf4j
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
    @Autowired
    JwtProperties jwtProperties;

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {

            //过滤方法
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                ServerHttpResponse response = exchange.getResponse();
                //1、判断请求的路径 是否是配置中的允许直接访问的无需验证登录的 地址，如果是则放行请求
                // http://sso.gmall.com/toLogin.html?returnUrl=http://www.gmall.com/
                // request.getURI().getPath() ==    /toLogin.html
                try {
                    String path = request.getURI().getPath();//请求报文中的访问的路径【服务器地址 后   ?参数前面的内容】
                    List<String> paths = config.getPaths();//配置的允许直接访问的资源的路径地址集合
                    if(paths.stream().anyMatch(configPath -> configPath.indexOf(path) != -1)){
                        //放行请求
                        return chain.filter(exchange);
                    }
                    ;
                    //2、验证登录状态
                    //获取cookie中的GMALL-TOKEN 并验证签名解析token中的ip
                    HttpCookie cookie = request.getCookies().getFirst(jwtProperties.getCookieTokenName());
                    if(cookie==null || StringUtils.isEmpty(cookie.getValue())){//获取token失败
                        //重定向到登录页面让用户登录
                        //=================
                        throw new GmallException("没有携带GMALL-TOKEN访问，需要登录");
                    }
                    //2.1 验证token是否有效(是否过期、是否被篡改)
                    String token = cookie.getValue();//获取GMALL-TOKEN的值
                    Map<String, Object> map = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
                    System.out.println("authFilter:  "+map);
                    //再验证token中的ip地址和当前请求的客户端ip是否一致，如果一致代表已登录   放行
                    String tokenIp = map.get("ip").toString();
                    //本次请求的客户端ip
                    String clientIp = IpUtils.getIpAddressAtGateway(request);
                    if(!clientIp.equals(tokenIp)){//ip和登录时的不匹配
                        //重定向到登录页面让用户登录
                        //=================
                        throw new GmallException("客户端IP和token的IP不匹配，需要重新登录: clientIp="+ clientIp+" , tokenIp: "+ tokenIp);
                    }
                    //token未过期、没有被篡改、代码没有异常、放行请求
                    return chain.filter(exchange);
                } catch (Exception e) {
                    e.printStackTrace();
                    log.warn(e.getMessage());
                    //2.2 token无效登录状态无效 拦截请求 重定向到登录页面
                    //重定向到登录页面让用户登录
                    //只要让浏览器去访问http://sso.gmall.com/toLogin.html?returnUrl=本地请求的地址
                    //向相应报文的头中添加一对头: 设置重定向地址
                    response.getHeaders().set(HttpHeaders.LOCATION ,
                            "http://sso.gmall.com/toLogin.html?returnUrl="+request.getURI());
                    //设置响应报文的状态码为302
                    response.setStatusCode(HttpStatus.SEE_OTHER);
                    return response.setComplete(); //使用上面的response对象返回一个响应报文  结束本次请求

                }
            }
        };
    }

    public AuthGatewayFilterFactory(){
        super(Config.class);
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("paths");
    }


    @Data
    public static class Config{
        private List<String> paths;
    }
}
